package org.example.services.impl;

import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.example.dao.SettingsDao;
import org.example.dao.UserDao;
import org.example.pojo.Setting;
import org.example.pojo.SobUser;
import org.example.response.ResponseResult;
import org.example.response.ResponseState;
import org.example.services.IUserService;
import org.example.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.Map;
import java.util.Random;

@Slf4j
@Service     //@Service，标记为Spring服务层组件
@Transactional  //@Transactional：添加事务管理
public class UserServiceImpl implements IUserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired  // 通过@Autowired自动注入，就是在前面@Bean配置的实例
    private IdWorker idWorker;

    @Autowired
    private UserDao userDao;

    @Autowired
    private SettingsDao settingsDao;

    /**
     * HttpServletRequest HTTP 请求的接口，它包含了客户端发送给服务器的所有信息：
     * 请求参数（GET/POST 参数）
     * 请求头信息
     * 会话信息
     * 客户端信息（IP 地址、浏览器信息等）
     * 请求方法（GET、POST 等）
     * URL 信息
     *
     * @param sobUser
     * @param request
     * @return
     */
    @Override
    public ResponseResult initManagerAccount(SobUser sobUser, HttpServletRequest request) {
        //检查是否有初始化
        Setting managerAccountState = settingsDao.findOneByKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        if (managerAccountState != null) {
            return ResponseResult.FAILURE("管理员账号已初始化了");
        }
        //检查数据
        if (TextUtils.isEmpty(sobUser.getUserName())) {
            return ResponseResult.FAILURE("用户名不能为空！");
        }

        if (TextUtils.isEmpty(sobUser.getPassword())) {
            return ResponseResult.FAILURE("密码不能为空!");
        }

        if (TextUtils.isEmpty(sobUser.getEmail())) {
            return ResponseResult.FAILURE("邮箱不能为空!");
        }

        //补充数据  //使用IdWorker生成分布式唯一ID
        sobUser.setId(String.valueOf(idWorker.nextId()));
        sobUser.setRoles(Constants.User.ROLE_ADMIN);
        sobUser.setAvatar(Constants.User.DEFAULT_AVATAR);
        sobUser.setState(Constants.User.DEFAULT_STATE);
        String remoteAddr = request.getRemoteAddr();
        String localAddr = request.getLocalAddr();
        log.info("remoteAddr:{} localAddr:{}", remoteAddr, localAddr);
        sobUser.setRegIp(remoteAddr);
        sobUser.setLoginIp(remoteAddr);
        sobUser.setCreateTime(new Date());
        sobUser.setUpdateTime(new Date());
        //对密码进行加密
        //原密码
        String password = sobUser.getPassword();
        //密码加密
        String encode = bCryptPasswordEncoder.encode(password);
        sobUser.setPassword(encode);
        //保存到数据库里
        userDao.save(sobUser);
        //更新已经添加的标记
        Setting setting = new Setting();
        setting.setKey(Constants.Settings.MANAGER_ACCOUNT_INIT_STATE);
        setting.setId(String.valueOf(idWorker.nextId()));
        setting.setCreateTime(new Date());
        setting.setUpdateTime(new Date());
        setting.setValue("1");
        settingsDao.save(setting);
        //改标记值
        return ResponseResult.SUCCESS("初始化成功");
    }

    @Autowired
    private Random random;
    public static final int[] captcha_font_types = {Captcha.FONT_1, Captcha.FONT_2, Captcha.FONT_3, Captcha.FONT_4, Captcha.FONT_5, Captcha.FONT_6, Captcha.FONT_7, Captcha.FONT_8, Captcha.FONT_9, Captcha.FONT_10,};
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 获取图灵验证码
     *
     * @param response
     * @param captchaKey
     * @throws Exception
     */
    @Override
    public void createCaptcha(HttpServletResponse response, String captchaKey) throws Exception {
        if (captchaKey.length() < 13) {
            return;
        }

        long key;
        try {
            key = Long.parseLong(captchaKey);
        } catch (Exception e) {
            return;
        }

        //可以用了
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        int captchaType = random.nextInt(2);    // 改为 0 或 1，不再生成 ArithmeticCaptcha算数类型
        Captcha targetCaptcha;
        if (captchaType == 0) {
            // 三个参数分别为宽、高、位数
            targetCaptcha = new SpecCaptcha(200, 60, 5);
        } else {
            //gif类型
            targetCaptcha = new GifCaptcha(200, 60);
        }/* else {
            //算数类型
            targetCaptcha = new ArithmeticCaptcha(200, 60);
            targetCaptcha.setLen(2);    //几位数运算，默认是两位数
        }*/

        // 设置字体
        int inidex = random.nextInt(captcha_font_types.length);

        targetCaptcha.setFont(captcha_font_types[inidex]);  // 有默认字体，可以不用设置
        // 设置类型，纯数字、纯字母、字母数字混合
        targetCaptcha.setCharType(Captcha.TYPE_DEFAULT);
        String content = targetCaptcha.text().toLowerCase();
        log.info("图灵验证码:{}", content);
        //保存到redis里，10分钟有效
        redisUtil.set(Constants.User.KEY_CAPTCHA_CONTENT + key, content, 60 * 10);

        // 输出图片流
        targetCaptcha.out(response.getOutputStream());
    }

    @Autowired
    private TaskService taskService;

    /**
     * 发送邮件验证码
     * 使用场景：注册，找回密码，修改邮箱（会输入新的邮箱）
     * 注册(register)：如果已经注册过了，就提示说，改邮箱已经注册
     * 找回密码(forget)：如果没有注册过，提示该邮箱没有注册
     * 修改邮箱(update)（新的邮箱）：如果已经注册了，提示该邮箱已经注册
     *
     * @param type
     * @param request
     * @param emailAddress
     * @return
     */
    @Override
    public ResponseResult sendEmail(String type, HttpServletRequest request, String emailAddress) {
        if (emailAddress == null) {
            ResponseResult.FAILURE("邮箱地址不可以为空！");
        }
        //根据类型，查询邮箱是否存在
        SobUser oneByEmail = userDao.findOneByEmail(emailAddress);
        if ("register".equals(type) || "update".equals(type)) {
            if (oneByEmail != null) {
                return ResponseResult.FAILURE("该邮箱已注册！");
            }
        } else if ("forget".equals(type)) {
            if (oneByEmail == null) {
                return ResponseResult.FAILURE("该邮箱暂未注册！");
            }
        }
        //1.防止暴力发送，同一个邮箱间隔要超过30秒发送一次，同一个ip，一小时内最多只能发送10次（如果是短信你最多只能发送五次）
        String remoteAddr = request.getRemoteAddr();
        log.info("sendEmail id:{}", remoteAddr);
        //拿出来，如果没有，那就过
        Integer ipSendTime = (Integer) redisUtil.get(Constants.User.KEY_EMAIL_SEND_ID + remoteAddr);
        if (ipSendTime != null && ipSendTime > 10) {
            return ResponseResult.FAILURE("发送验证码太频繁了！");
        }
        Object hasEmailSend = redisUtil.get(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress);
        if (hasEmailSend != null) {
            return ResponseResult.FAILURE("发送验证码太频繁了！");
        }
        //2.检查邮箱地址是否正确
        boolean emailAddressOk = TextUtils.isEmailAddressOk(emailAddress);
        if (!emailAddressOk) {
            return ResponseResult.FAILURE("邮箱格式不正确");
        }
        //0~999999
        int code = random.nextInt(999999);
        if (code < 100000) {
            code += 10000;
        }
        log.info("邮箱验证码:{}", code);
        //3.发送验证码 ，6位数：100000~999999
        try {
            taskService.sendEmailVerifyCode(String.valueOf(code), emailAddress);
        } catch (Exception e) {
            return ResponseResult.FAILURE("验证码发送失败，请稍后重试！");
        }
        //4.做记录，发送记录，code
        if (ipSendTime == null) {
            ipSendTime = 0;
        }
        ipSendTime++;
        //1.个小时有效期
        redisUtil.set(Constants.User.KEY_EMAIL_SEND_ID + remoteAddr, ipSendTime, 60 * 60);
        redisUtil.set(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress, "true", 30);
        //保存code,10分钟有效
        redisUtil.set(Constants.User.KEY_EMAIL_CODE_CONTENT + emailAddress, String.valueOf(code), 60 * 10);
        return ResponseResult.SUCCESS("验证码发送成功！");
    }

    /**
     * 注册
     *
     * @param sobUser
     * @param emailCode
     * @param captcha
     * @param captchaKey
     * @param request
     * @return
     */
    @Override
    public ResponseResult register(SobUser sobUser, String emailCode, String captcha,
                                   String captchaKey, HttpServletRequest request) {
        //检查当前用户名是否注册
        String userName = sobUser.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return ResponseResult.FAILURE("用户名不可以为空！");
        }
        //检查用户名是否存在
        SobUser oneByName = userDao.findOneByUserName(userName);
        if (oneByName != null) {
            return ResponseResult.FAILURE("用户名已注册！");
        }
        //检查邮箱格式是否正确
        String email = sobUser.getEmail();
        if (TextUtils.isEmpty(email)) {
            return ResponseResult.FAILURE("邮箱地址不可以为空！");
        }
        boolean emailAddressOk = TextUtils.isEmailAddressOk(email);
        if (!emailAddressOk) {
            return ResponseResult.FAILURE("邮箱格式不正确！");
        }
        //检查该邮箱是否已经注册
        SobUser oneByEmail = userDao.findOneByEmail(email);
        if (oneByEmail != null) {
            return ResponseResult.FAILURE("该邮箱已注册！");
        }
        //检查邮箱验证码是否正确
        String emailVerifyCode = (String) redisUtil.get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (TextUtils.isEmpty(emailVerifyCode)) {
            return ResponseResult.FAILURE("邮箱验证码已过期！");
        }
        if (!emailCode.equals(emailVerifyCode)) {
            return ResponseResult.FAILURE("邮箱验证码不正确！");
        } else {
            //正确，干掉redis里面的内容
            redisUtil.del(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        }
        //检查图灵验证码是否正确
        String captchaVerifyCode = (String) redisUtil.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (TextUtils.isEmpty(captchaVerifyCode)) {
            return ResponseResult.FAILURE("验证码已过期！");
        }
        if (!captcha.equals(captchaVerifyCode)) {
            return ResponseResult.FAILURE("验证码不正确！");
        } else {
            //正确，干掉redis里面的内容
            redisUtil.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        }
        //达到可以注册的条件
        //对密码进行加密
        //原密码
        String password = sobUser.getPassword();
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILURE("密码不能为空！");
        }
        //密码加密
        String encode = bCryptPasswordEncoder.encode(password);
        sobUser.setPassword(encode);
        //补全数据包括：注册id，登录id，角色，头像，创建时间，更新时间
        //补充数据  //使用IdWorker生成分布式唯一ID
        sobUser.setId(String.valueOf(idWorker.nextId()));
        sobUser.setRoles(Constants.User.ROLE_NORMAL);
        sobUser.setAvatar(Constants.User.DEFAULT_AVATAR);
        sobUser.setState(Constants.User.DEFAULT_STATE);
        String remoteAddr = request.getRemoteAddr();
        sobUser.setRegIp(remoteAddr);
        sobUser.setLoginIp(remoteAddr);
        sobUser.setCreateTime(new Date());
        sobUser.setUpdateTime(new Date());
        //保存到数据库
        userDao.save(sobUser);
        log.info("注册成功！");
        //返回结果
        return ResponseResult.GET(ResponseState.JOIN_IN_SUCCESS);
    }

    /**
     * 登录
     *
     * @param captcha
     * @param captchaKey
     * @param sobUser
     * @param request
     * @param response
     * @return
     */
    @Override
    public ResponseResult doLogin(String captcha, String captchaKey, SobUser sobUser, HttpServletRequest request, HttpServletResponse response) {

        String captchaString = (String) redisUtil.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (!captcha.equals(captchaString)) {
            return ResponseResult.FAILURE("验证码不正确！");
        }

        //有可能是邮箱，也有可能是用户名
        String userName = sobUser.getUserName();
        if (TextUtils.isEmpty(userName)) {
            return ResponseResult.FAILURE("账户不可以为空！");
        }

        String password = sobUser.getPassword();
        if (TextUtils.isEmpty(password)) {
            return ResponseResult.FAILURE("密码不能为空！");
        }

        SobUser userFromDb = userDao.findOneByUserName(userName);
        if (userFromDb == null) {
            userFromDb = userDao.findOneByUserName(userName);
        }

        if (userFromDb == null) {
            return ResponseResult.FAILURE("用户名或密码不正确！");
        }

        //用户存在
        //对比密码
        boolean matches = bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
        if (!matches) {
            return ResponseResult.FAILURE("用户名或密码不正确！");
        }

        //密码是正确的
        //判断用户状态，如果是非正常的状态，则返回结果
        if (!"1".equals(userFromDb.getState())) {
            return ResponseResult.FAILURE("当前账户已被禁止！");
        }
        //生成token
        Map<String, Object> claims = ClaimsUtils.sobUser2Claims(userFromDb);
        //token默认有效期2小时
        String token = JwtUtil.createToken(claims);
        //返回token的MD5值，token会保存在redis里
        //前端访问的是时候，携带token的MD5key，从redis中获取即可
        String tokenKey = DigestUtils.md5DigestAsHex(token.getBytes());
        //保存token到redis里，有效期为两小时，key是tokenKey
        redisUtil.set(Constants.User.KEY_TOKEN + tokenKey, token, 60 * 60 * 2);
        //把tokenKey写道cookies里
        CookieUtils.setUpCookie(response, Constants.User.COOKIE_TOKEN_KEY, tokenKey);
        return ResponseResult.SUCCESS("登录成功！");
    }
}
